import {callFunction, Config, TokenResType, WebsocketService} from "../type";
import {buildUUID, isFunction, isNull, isPromise} from "../utils";
import {HeartCheckLoop} from "./HeartCheckLoop";
import {SocketEvent} from "./SocketEvent";
import {ExecutorManager} from "./ExecutorManager";
import {MsgType} from "../constants";

export class WebsocketServiceImpl implements WebsocketService {
    private static websocketService: WebsocketServiceImpl = null;
    ws: WebSocket;//socket实例对象
    config: Config;
    event: SocketEvent;
    lockReconnect: boolean;
    methodNameMap: Map<String, callFunction>;
    cmdIdMap = new Map<string, ExecutorManager<any>>();
    heartCheck: HeartCheckLoop;

    static get install() {
        if (!this.websocketService) this.websocketService = new WebsocketServiceImpl();
        return this.websocketService;
    }

    constructor() {
        this.heartCheck = new HeartCheckLoop(this);
        this.event = new SocketEvent(this);
    }


    private wsApi: string;

    init(config: Config) {
        this.config = config;
        if (isNull(config.wsApi)) {
            console.error("wsApi is not null")
            throw new Error("wsApi is not null");
        }

        if (!isFunction(this.config.getToken)) {
            console.error("getToken not Function")
            throw new Error("getToken not Function");
        }
        return new Promise((resolve,reject) => {
            this.initialWebSocket(resolve,reject);
        })
    }

    getUserToken(): Promise<string> {
        if (!isFunction(this.config.getToken)) {
            console.error("getToken not Function")
            throw new Error("getToken not Function");
        }

        let tokenRes: TokenResType = this.config.getToken();

        if (isPromise(tokenRes)) {
            return tokenRes;
        } else if (isNull(tokenRes)) {
            return Promise.resolve<string>(tokenRes as string);
        } else {
            return Promise.reject("参数有误！");
        }
    }


    initialWebSocket(resolve: Function=()=>{},reject:Function=()=>{}) {
        if (this.ws === null) {
            this.getUserToken().then((token) => {
                if (!this.config.wsApi) {
                    throw new Error("config.wsApi为空socket连接失败！！");
                }

                if(isNull(token)){
                    throw new Error("token为空，连接失败！");
                }

                const userId = buildUUID();
                const domainURL = this.config.wsApi;
                const domain = domainURL.replace('https://', 'wss://').replace('http://', 'ws://');
                const url = `${domain}/chess/${userId}?token=${token}`;
                this.ws = new WebSocket(url);
                this.ws.onopen = this.event.open.bind(this.event);
                this.ws.onerror = this.event.error.bind(this.event);
                this.ws.onmessage = this.event.message.bind(this.event);
                this.ws.onclose = this.event.close.bind(this.event);
                this.event.onOpen().then(() => {
                    if (resolve) {
                        resolve();
                    }
                })
            }).catch((error) => {
                reject(error);
            })
        }
    }

    doSuccess(cmdId, name, data) {
        let executor = this.cmdIdMap.get(cmdId);
        if (executor) {
            executor.doResolve(data);
            this.cmdIdMap.delete(cmdId);
            return;
        }
        this.methodNameMap.get(name)?.forEach(fn => fn(data))
    }

    callMethod(name, params) {
        return new Promise((resolve, reject) => {
            const cmdId = buildUUID();
            this.cmdIdMap.set(cmdId, new ExecutorManager(resolve, reject));
            try {
                let ws = this.ws;
                if (ws != null && ws.readyState === ws.OPEN) {
                    ws.send(
                        JSON.stringify({
                            'type': MsgType.CALL_METHOD,
                            'method_name': name,
                            'method_id': cmdId,
                            'call_params': params,
                        })
                    );
                }
            } catch (err: any) {
                console.warn('【websocket】发送消息失败：(' + err.code + ')');
            }
        })
    }

    reconnect() {
        if (this.lockReconnect) return;
        this.lockReconnect = true;
        setTimeout(() => {
            if (this.ws && this.ws.close) {
                this.ws.close();
            }
            this.ws = null;
            console.info('【websocket】尝试重连...');
            this.initialWebSocket();
            this.lockReconnect = false;
        }, 5000);
    }

    sendMessage(type, message) {
        try {
            let ws = this.ws;
            if (ws != null && ws.readyState === ws.OPEN) {
                ws.send(
                    JSON.stringify({
                        type: type,
                        data: message,
                    })
                );
            }
        } catch (err: any) {
            console.warn('【websocket】发送消息失败：(' + err.code + ')');
        }
    }

    doError(cmdId: any, name: any, msg: any): void {
        let executor = this.cmdIdMap.get(cmdId);
        if (executor) {
            executor.doReject(msg);
            this.cmdIdMap.delete(cmdId);
            return;
        }

        this.methodNameMap.get(name)?.forEach(fn => fn(msg));
    }
}


export const socketService = WebsocketServiceImpl.install;
